﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using Dycox.Data;
using Dycox.Linq;
using Dycox.Linq.Data;
using System.Data.Common;

namespace Dycox.Workflows.Data
{
    internal class LinqToSqlDataProvider : DataProvider
    {
        public LinqToSqlDataProvider(DataContext dataContext)
            : base(dataContext)
        {
        }

        private DataContext DataContext
        {
            get { return (DataContext)base.DataSource; }
        }

        internal override bool TransactionExists
        {
            get
            {
                return DataContext.Transaction != null;
            }
        }

        public override void Insert(object obj)
        {
            DataContext.GetTable(obj.GetType()).InsertOnSubmit(obj);
        }

        public override IWorkflowComponent GetComponent(ComponentIdentifier id, bool throwError)
        {
            IWorkflowComponent component;

            if (id.ThreadId != null)
            {
                if (id.StepSequence != null)
                    component = WFSteps.SingleOrDefault(o => o.ProcessId == id.ProcessId && o.ThreadId == id.ThreadId && o.Sequence == id.StepSequence);
                else
                    component = WFThreads.SingleOrDefault(o => o.ProcessId == id.ProcessId && o.ThreadId == id.ThreadId);
            }
            else
                component = WFProcesses.SingleOrDefault(o => o.ProcessId == id.ProcessId);

            if (component == null && throwError)
                throw new ArgumentException(SR.GetString("invalid_component_id"), "id");

            return component;
        }

        public override void DeleteComponent(ComponentIdentifier id)
        {
            object component = GetComponent(id, false);
            if (component != null)
            {
                DataContext.GetTable(component.GetType()).DeleteOnSubmit(component);
            }
        }

        public override void DeepDeleteProcess(WFProcess process)
        {
            
            DataContext.GetTable<WFStep>().DeleteAllOnSubmit(process.ThreadsInternal.SelectMany(o => o.StepsInternal));
            DataContext.GetTable<WFThread>().DeleteAllOnSubmit(process.ThreadsInternal);
            DataContext.GetTable<WFProcess>().DeleteOnSubmit(process);
        }

        public override IQueryable<WFThread> WFThreads
        {
            get { return DataContext.GetTable<WFThread>(); }
        }

        public override IQueryable<WFProcess> WFProcesses
        {
            get { return DataContext.GetTable<WFProcess>(); }
        }

        public override IQueryable<WFStep> WFSteps
        {
            get { return DataContext.GetTable<WFStep>(); }
        }


        protected override void SubmitChangesCore()
        {
            DataContext.SubmitChanges();
        }

        internal override bool HasChanges
        {
            get
            {
                ChangeSet set = DataContext.GetChangeSet();
                if (set.Updates.Count > 0 || set.Inserts.Count > 0 || set.Deletes.Count > 0)
                    return true;
                else
                    return false;
                
            }
        }

        public override WFFrame PopFrame(Guid processId)
        {
            var tbl = DataContext.GetTable<WFFrame>();

            WFFrame frame = tbl.Where(o => o.ProcessId == processId).OrderByDescending(o => o.Sequence).FirstOrDefault();

            if (frame != null)
                tbl.DeleteOnSubmit(frame);

            return frame;
        }

        public override void PushFrame(WFFrame frame)
        {
            var tbl =  DataContext.GetTable<WFFrame>();
            //var seq = tbl.Count(o => o.ProcessId == frame.ProcessId);

            //frame.Sequence = seq;

            tbl.InsertOnSubmit(frame);
        }

        internal override void FillUndoState(WorkflowUndoState state, WorkflowExecutionContext context)
        {
            var dc = this.DataContext;

            ChangeSet set = dc.GetChangeSet();

            foreach (object obj in set.Inserts)
                state.AddInsert(obj);

            foreach (object obj in set.Deletes)
                state.AddDelete(obj);

            foreach (object obj in set.Updates)
            {
                EntityChangeInfo info = DataContext.GetChangeInfo(obj);
                state.AddUpdate(info);
            }
        }

        public override void ClearFrames(Guid processId)
        {
            var table = DataContext.GetTable<WFFrame>();

            table.DeleteAllOnSubmit(table.Where(o => o.ProcessId == processId));

        }

        public override void InitializeStepQuery(DataArguments args)
        {
            var loadref = args.LoadReferences;

            if (loadref != LoadReferences.None)
            {
                DataLoadOptions options = new DataLoadOptions();

                if ((loadref & LoadReferences.Parents) == LoadReferences.Parents)
                {
                    options.LoadWith<WFStep>(o => o.Thread);
                    options.LoadWith<WFThread>(o => o.Process);
                }

                DataContext.LoadOptions = options;
            }

        }

        public override void InitializeProcessQuery(Dycox.Workflows.Data.DataArguments args)
        {
            var lr = args.LoadReferences;

            if ((lr & LoadReferences.Children) == LoadReferences.Children)
            {
                DataLoadOptions options = new DataLoadOptions();

                options.LoadWith<WFProcess>(o => o.ThreadsInternal);
                options.LoadWith<WFThread>(o => o.StepsInternal);

                DataContext.LoadOptions = options;
            }
        }

        public override WFProcess[] GetChildProcesses(Guid parentId)
        {
            return DataContext.GetTable<WFProcess>().Where(o => o.ParentProcessId == parentId).ToArray();
        }

        public override void InitializeThreadQuery(Dycox.Workflows.Data.DataArguments args)
        {
            var loadref = args.LoadReferences;

            if (loadref != LoadReferences.None)
            {
                DataLoadOptions options = new DataLoadOptions();

                if ((loadref & LoadReferences.Children) == LoadReferences.Children)
                {
                    options.LoadWith<WFThread>(o => o.StepsInternal);
                }

                if ((loadref & LoadReferences.Parents) == LoadReferences.Parents)
                {
                    options.LoadWith<WFThread>(o => o.Process);
                }

                DataContext.LoadOptions = options;
            }

        }

        internal override EntityKey GetEntityKey(object entity)
        {
            return Dycox.Linq.Data.LinqToSqlHelpers.GetKey(DataContext, entity);
        }

        internal override object GetEntity(EntityKey key, Type type)
        {
            return DataContext.GetEntity(key, type);
        }

        internal override void DeleteEntity(EntityKey key, Type type)
        {
            object entity = GetEntity(key, type);

            DataContext.GetTable(type).DeleteOnSubmit(entity);
        }

        DbTransaction _transaction;
        bool _ownsTransaction;

        internal override void BeginTransaction()
        {
            if (_transaction == null)
            {
                DataContext dc = this.DataContext;
                _transaction = dc.Transaction;
                if (_transaction == null)
                {
                    var conn = dc.Connection;
                    if (conn.State == System.Data.ConnectionState.Closed)
                        conn.Open();
                    _transaction = dc.Connection.BeginTransaction();
                    dc.Transaction = _transaction;
                    _ownsTransaction = true;
                }
            }
        }

        internal override void CommitTransaction()
        {
            if (_transaction != null)
                _transaction.Commit();
        }

        internal override void RollbackTransaction()
        {
            if (_transaction != null)
                _transaction.Rollback();
        }

        public override void Dispose()
        {
            base.Dispose();
            if (_transaction != null && _ownsTransaction)
            {
                _transaction.Dispose();
            }
        }
    }
}
